#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Sesion.h"
#include "Thread.h"
#include "ProtocoloConstantes.h"

using namespace TP::Threading;

using namespace std;
using namespace TP::Comunicacion;

#define CONEXION_FINALIZADA 0
#define CONEXION_ERROR -1

Sesion::Sesion(int pFileDescriptor, struct sockaddr_in* pClientAddress) {
	this->fileDescriptor = pFileDescriptor;
	this->clientAddress = pClientAddress;
}

Sesion::~Sesion() {
	if (this->clientAddress)
		delete this->clientAddress;
}

void Sesion::agregarMensajeEntranteManejador(
		IMensajeEntranteManejador* pManejador) {
	if (pManejador)
		this->mensajeSesionManejadores.push_back(pManejador);
}

void Sesion::agregarManejadorSesionEvento(
		IManejadorSesionEvento* pManejador) {
	if (pManejador)
		this->manejadorSesionEvento = pManejador;
}

void Sesion::Iniciar() throw (Excepcion&) {
	try {
		this->conexionFinalizada = false;
		
		// Inicio la tarea de la sesion.
		Thread::Iniciar();
	}
	catch (Excepcion& pEx) {
		throw;
	}
}

void Sesion::Finalizar() throw (Excepcion&) {
	try {
		// Cierro la conexion en caso de que se encuentre activa.        
		if (!this->conexionFinalizada) {
			this->conexionFinalizada = true;
			shutdown(this->fileDescriptor, SHUT_RDWR);
		}
		// Finalizo las operaciones.
		Thread::Finalizar();
	}
	catch (Excepcion& pEx) {
		throw;
	}
}

void Sesion::Responder(const string& pMensaje) throw (Excepcion&) {
	// Codifico el mensaje.
	string msg_snd = pMensaje;
	for (unsigned int i = 0; i < msg_snd.size(); i++) {
		switch (msg_snd[i]) {
			case '\n':
				msg_snd[i] = SALTO_LINEA;
		}
	}
	msg_snd += FIN_MENSAJE;
	int n = send(this->fileDescriptor, msg_snd.c_str(), msg_snd.size(), 0);
	if (n == -1)
		throw Excepcion("ERROR writing to socket");

	else if (n == 0)
		throw Excepcion("Conexion perdida.");
}

void Sesion::tarea() {
	/* La sesion tiene como tarea recibir los mensajes entrantes, desencodearlos
	 * y notificar el mensaje a sus suscriptores.
	 * 
	 * La tarea es valida siempre y cuando la conexion no haya sido finaliada
	 * por el host.
	 */
	string mensaje = "";
	char buffer [256];
	int bytes_leidos = -1;
	int flags = 0;
	this->errorConexion = false;

	while (!this->conexionFinalizada && !this->errorConexion) {
		// Inicializo el buffer para lectura.
		bzero(buffer, 256);

		bytes_leidos = recv(this->fileDescriptor, buffer, 255, flags);

		if (bytes_leidos <= 0) {
			this->manejarErrorComunicacion(bytes_leidos);
			continue;
		}

		for (int i = 0; i < bytes_leidos; i++) {

			if (FIN_MENSAJE == buffer[i]) {
				// Notifico el mensaje a los servicios.
				for (vector<IMensajeEntranteManejador*>::iterator it = 
						this->mensajeSesionManejadores.begin(); it !=
						this->mensajeSesionManejadores.end(); ++it)
					
					(*it)->mensajeEntranteManejador((IMensajero*) this, mensaje);

				mensaje = "";
			}
			else if ('\r' == buffer[i] || '\n' == buffer[i])
				int a = 0;  // @todo: Eliminar el ese if cuando pase a
							// produccion. Se encuentra para compatibilidad
							// con telnet en tiempo de desarrollo/testing.
			else
				mensaje += buffer[i];
		}
	}
}

void Sesion::manejarErrorComunicacion(int pEstadoComunicacion) {
	if (CONEXION_FINALIZADA == pEstadoComunicacion) {
		// Cierro el descriptor.
		close(this->fileDescriptor);

		// Configuro el flag acorde a situacion.
		this->conexionFinalizada = true;
	}
	else if (CONEXION_ERROR == pEstadoComunicacion) {
		if (!this->conexionFinalizada) {
			string msg = "Error de lectura del host ";
			msg += inet_ntoa(this->clientAddress->sin_addr);
			msg += ".";
			this->manejadorSesionEvento->eventoErrorComunicacion((
					IEnviante*)this, msg);
		}
		this->errorConexion = true;
	}
}